`ifndef DIV_V
`define DIV_V


`include "defines.v"

// 除法模块
// 试商法实现32位整数除法
// 每次除法运算至少需要33个时钟周期才能完成
module div(
    input wire clk,
    input wire rstn,

    // from ex
    input wire[31:0] dividend_i,      // 被除数
    input wire[31:0] divisor_i,       // 除数
    input wire start_i,                  // 开始信号，运算期间这个信号需要一直保持有效
    input wire[2:0] op_i,                // 具体是哪一条指令
    input wire[`RegAddrWidth - 1 : 0] reg_waddr_i, // 运算结束后需要写的寄存器

    // to ex
    output reg[31:0] result_o,        // 除法结果，高32位是余数，低32位是商
    output reg ready_o,                  // 运算结束信号
    output reg busy_o,                  // 正在运算信号
    output reg[`RegAddrWidth - 1 : 0] reg_waddr_o  // 运算结束后需要写的寄存器
);

// 状态定义
localparam IDLE  = 4'b0001;
localparam START = 4'b0010;
localparam CALC  = 4'b0100;
localparam FINISH= 4'b1000;

reg[3:0] state;

reg[2:0] r_op; // 除法类型
reg[31:0] r_divisor; // 被除数
reg[31:0] r_dividend; // 除数
reg[31:0] r_quotient; // 商
reg[31:0] r_remainder; // 余数
reg[31:0] r_minuend; // 被减数，试商法

reg r_invert_result; // 是否结果需要翻转
reg[31:0] r_counter;

wire[31:0] dividend_invert;
wire[31:0] divisor_invert;
assign dividend_invert = (~r_dividend) + 1'b1;
assign divisor_invert = (~r_divisor) + 1'b1;

wire op_div;
wire op_divu;
wire op_rem;
wire op_remu;
assign op_div   = (r_op == `INST_DIV) ? 1'b1 : 1'b0;
assign op_divu  = (r_op == `INST_DIVU) ? 1'b1 : 1'b0;
assign op_rem   = (r_op == `INST_REM) ? 1'b1 : 1'b0;
assign op_remu  = (r_op == `INST_REMU) ? 1'b1 : 1'b0;

wire minuend_ge_divisor;
wire[31:0] minuend_sub_res;
wire[31:0] div_result_temp;
wire[31:0] minuend_temp;
assign minuend_ge_divisor = r_minuend >= r_divisor;
assign minuend_sub_res = r_minuend - r_divisor;
assign div_result_temp = minuend_ge_divisor ? ({r_quotient[30:0], 1'b1}) : ({r_quotient[30:0], 1'b0});
assign minuend_temp = minuend_ge_divisor ? minuend_sub_res[30:0] : r_minuend[30:0];

// 状态机实现
always @ (posedge clk) begin
    if (rstn == 1'b0) begin
        state       <= IDLE;
        result_o    <= 'd0;
        ready_o     <= 1'b0;
        busy_o      <= 1'b0;
        reg_waddr_o <= 'd0;
        r_op <= 'd0;
        r_divisor <= 'd0;
        r_dividend <= 'd0;
        r_quotient <= 'd0;
        r_remainder <= 'd0;
        r_minuend <= 'd0;
        r_invert_result <= 1'b0;
        r_counter <= 'd0;
    end else begin
        case (state)
            IDLE: begin
                if (start_i == 1'b1) begin
                    r_op <= op_i;
                    r_divisor <= divisor_i;
                    r_dividend <= dividend_i;
                    reg_waddr_o <= reg_waddr_i;
                    busy_o <= 1'b1;
                    state <= START;
                end else begin
                    result_o    <= 'd0;
                    ready_o     <= 1'b0;
                    busy_o      <= 1'b0;
                    reg_waddr_o <= 'd0;
                    r_op <= 'd0;
                    r_divisor <= 'd0;
                    r_dividend <= 'd0;
                end
            end
            START: begin
                if (start_i == 1'b1) begin
                    // 除数为0
                    if(r_divisor == 'd0) begin
                        if((op_div == 1'b1) || (op_divu == 1'b1)) begin
                            result_o <= 32'hFFFF_FFFF;
                        end else begin
                            result_o <= r_dividend;
                        end
                        ready_o <= 1'b1;
                        busy_o  <= 1'b0;
                        state   <= IDLE;
                    // 除数不为0
                    end else begin
                        r_quotient <= 'd0;
                        r_remainder <= 'd0;
                        r_counter <= 32'h4000_0000;
                        
                        // DIV和REM这两条指令是有符号数运算指令
                        if((op_div == 1'b1) || (op_rem == 1'b1)) begin
                            // 被除数求补码
                            if (r_dividend[31] == 1'b1) begin
                                r_dividend <= dividend_invert;
                                r_minuend <= dividend_invert[31];
                            end else begin
                                r_minuend <= r_dividend[31];
                            end
                            // 除数求补码
                            if (r_divisor[31] == 1'b1) begin
                                r_divisor <= divisor_invert;
                            end
                        end else begin
                            r_minuend <= r_dividend[31];
                        end

                        // 运算结束后是否要对结果取补码
                        if(((op_div == 1'b1) && (r_dividend[31] ^ r_divisor[31] == 1'b1))
                            || ((op_rem == 1'b1) && (r_dividend[31] == 1'b1))) begin
                            r_invert_result <= 1'b1;
                        end else begin
                            r_invert_result <= 1'b0;
                        end
                        busy_o <= 1'b1;
                        state <= CALC;
                    end
                end else begin
                    result_o <= 'd0;
                    ready_o <= 1'b0;
                    busy_o  <= 1'b0;
                    state   <= IDLE;
                end
            end
            CALC: begin
                if(start_i == 1'b1) begin
                    r_dividend <= {r_dividend[30:0], 1'b0};
                    r_quotient <= div_result_temp;
                    r_counter <= {1'b0, r_counter[31:1]};
                    if(|r_counter == 1'b1) begin
                        r_minuend <= {minuend_temp[30:0], r_dividend[30]};
                    end else begin
                        state <= FINISH;
                        if(minuend_ge_divisor == 1'b1) begin
                            r_remainder <= minuend_sub_res;
                        end else begin
                            r_remainder <= r_minuend;
                        end
                    end
                end else begin
                    result_o <= 'd0;
                    ready_o <= 1'b0;
                    busy_o  <= 1'b0;
                    state   <= IDLE;
                end
            end
            FINISH: begin
                if(start_i == 1'b1) begin
                    if((op_div == 1'b1) || (op_divu == 1'b1)) begin
                        if(r_invert_result == 1'b1) begin
                            result_o <= (~r_quotient) + 1'b1;
                        end else begin
                            result_o <= r_quotient;
                        end
                    end else begin
                        if(r_invert_result == 1'b1) begin
                            result_o <= (~r_remainder) + 1'b1;
                        end else begin
                            result_o <= r_remainder;
                        end
                    end
                    ready_o <= 1'b1;
                    busy_o  <= 1'b0;
                    state   <= IDLE;
                end else begin
                    result_o <= 'd0;
                    ready_o <= 1'b0;
                    busy_o  <= 1'b0;
                    state   <= IDLE;
                end
            end
        endcase
    end
end

endmodule


`endif // DIV_V